package vista;

import modelo.Automata;
import modelo.Edge;
import modelo.Node;
import vista.modelo.*;
import vista.interfaces.EventDispatcher;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import co.edu.icesi.tv.game.Game;
import co.edu.icesi.tv.game.ImageDictionary;

public class Tablero extends Game {

	// Model
	private Automata automata;
	private char nombreNodoConsecutivo = 'A';
	private VNode selected, toJoin;

	// Variables de ventana
	public int xWindow, yWindow;
	private int lastXwindow, lastYwindow;
	private int xAbsolute;
	//	private int yAbsolute;

	// Variables de nodo
	private int lastXnode, lastYnode, biggestXmove, biggestYmove;
	private boolean onGestureTime;

	private String msj = "";

	// Images
	private ImageDictionary resources;
	private Paint paint;

	// State variables
	private boolean touching, enterDeleteZone, popUpBlocker;
	//	private boolean drag.left, drag.rigth;

	// Draging window
	private WindowLayer window;

	// Delete button
	private int DELETE_Y;
	private final int DELETE_X, DELETE_RADIUS;
	private int deleteRadius;

	// Constantes
	private static final int WORLD_SIZE = 500;
	private static final int NODE_RADIUS = 40;

	// Event dispatcher
	private EventDispatcher eventDispatcher;

	public Tablero(int windowWidth, int windowHeight, ImageDictionary resources, EventDispatcher eventDispatcher, Automata automata) {
		super(windowWidth, windowHeight);

		// Receiving resources
		this.resources = resources;
		this.eventDispatcher = eventDispatcher;
		this.automata = automata;

		// Delete positions
		DELETE_X = windowWidth/2;
		DELETE_RADIUS = resources.getImage(1).getWidth()/2;
		DELETE_Y = windowHeight - DELETE_RADIUS*2;
		deleteRadius = DELETE_RADIUS;

		// Pincel
		paint = new Paint();
		paint.setTextSize(20);

		// Drag layer
		window = new WindowLayer(width, height, eventDispatcher, resources);
	}

	public void changeAutomata(Automata automata) {
		this.automata = automata;
		this.window.setDownOptionMenu();
	}

	public void moveToOrigin() {
		this.xWindow = 0;
		this.yWindow = 0;
	}

	@Override
	protected void refresh() {

		// Manejando la zona de borrado
		if(selected == null)
			DELETE_Y = height;
		else
			DELETE_Y = (DELETE_Y > height - DELETE_RADIUS*2 ? DELETE_Y - 30 : height - DELETE_RADIUS*2);

	}

	@Override
	protected void move(Object[] interactive) {

		int touch[] = (int[])interactive[0];

		moveDesordenado(touch);

		// Controlando los limites del mapa
		if(!window.leftDrag && !window.rigthDrag) {
			if(xWindow > WORLD_SIZE)
				xWindow = WORLD_SIZE;
			else if(xWindow < 0)
				xWindow = 0;
			if(yWindow > WORLD_SIZE)
				yWindow = WORLD_SIZE;
			else if(yWindow < 0)
				yWindow = 0;
		}

	}

	private void moveDesordenado(int touch[]) {

		// Guardando la posicion absoluta del toque
		xAbsolute = touch[InterfazControl.X_MOVE];
		//		yAbsolute = touch[InterfazControl.Y_MOVE];

		// Update view
		if(touch[InterfazControl.UPDATE_VIEW] > 0)
			updateEdges();

		// Agregar nuevo nodo dando doble touch
		if(!window.isOptionMenuOpen() && touch[InterfazControl.TOUCH_UP_COUNTER] == 2) {

			// Reiniciando el contador de touch
			touch[InterfazControl.TOUCH_UP_COUNTER] = 0;

			if(selected == null) {

				// Buscando si se toco un nodo
				findNodeByPosition(touch[InterfazControl.X_POS] + xWindow, touch[InterfazControl.Y_POS] + yWindow);
				if(selected == null) {
					// Creando un nuevo nodo
					automata.nodes.add(new VNode(""+nombreNodoConsecutivo, touch[InterfazControl.X_POS] + xWindow, touch[InterfazControl.Y_POS] + yWindow, NODE_RADIUS));
					nombreNodoConsecutivo ++;
				} else {
					// Cambiar valor de aceptacion del nodo
					selected.acceptor = ! selected.acceptor;
					return;
				}

			} else {
				// Cambiar valor de aceptacion del nodo
				selected.acceptor = ! selected.acceptor;
				return;
			}
		}

		// Detectando toques
		if(touching) {

			// Toque sobre el mapa
			if(selected == null) {

				// Moving window
				moveWindow(touch);
			}

			// Toque sobre un nodo
			else {

				// Guardando posicion anterior
				int lastX = selected.x;
				int lastY = selected.y;

				// Moviendo un nodo
				selected.setPosition(xWindow + touch[InterfazControl.X_MOVE], yWindow + touch[InterfazControl.Y_MOVE]);

				// Almacenando el patron de movimiento
				int moveX = selected.x - lastXnode;
				int moveY = selected.y - lastYnode;
				biggestXmove = (moveX > biggestXmove ? moveX : biggestXmove);
				biggestYmove = (moveY > biggestYmove ? moveY : biggestYmove);

				// Finding nodes to join
				findNodeToJoin();

				// Bloqueador por movimientos largos
				if(!popUpBlocker)
					popUpBlocker = touch[InterfazControl.TOUCH_TIMER] > InterfazControl.TIME_THREAD*3 && Geometry.distance(selected.x, selected.y, lastX, lastY) > selected.radius/2;

					if(!popUpBlocker && touch[InterfazControl.TOUCH_TIMER] > InterfazControl.TIME_THREAD*6) {
						window.setUpOptionMenu(selected);
						//						eventDispatcher.dispatchEvent(new Object[]{selected}, InterfazControl.EDIT_NODE);
						//						resetFlags();
						//						selected = null;
						//						return;
					}

					// Enter delete zone
					enterDeleteZone = Geometry.distance(touch[InterfazControl.X_MOVE], touch[InterfazControl.Y_MOVE], DELETE_X, DELETE_Y) < selected.radius*4;
					if(enterDeleteZone) {
						if(deleteRadius < DELETE_RADIUS*4)
							deleteRadius += 20;
						selected.y += deleteRadius;
					} else
						deleteRadius = DELETE_RADIUS;
			}
		}

		// Sin tocar
		else {
			resetFlags();
		}

		// Redefiniendo la condicion de toque
		if(!touching) {

			// Definicion
			touching = touch[InterfazControl.TOUCH_TIMER] > 0;

			// Encontrar el nodo tocado
			if(touching) {

				// Toque del option menu
				if(window.collide(touch[InterfazControl.X_POS], touch[InterfazControl.Y_POS]))
					return;

				// Toque los bordes
				if(!window.guessDragging(xAbsolute)) {

					// Buscando si se toco un nodo
					findNodeByPosition(touch[InterfazControl.X_POS] + xWindow, touch[InterfazControl.Y_POS] + yWindow);

					// Encontrar la arista tocada
					if(selected == null) {
						VEdge edge = getEdgeTouched(touch[InterfazControl.X_POS], touch[InterfazControl.Y_POS]);
						if(edge != null) {
							eventDispatcher.dispatchEvent(new Object[]{edge}, InterfazControl.EDIT_EDGE);
						}
					} else {
						// Guardando la posicion del nodo
						lastXnode = selected.x;
						lastYnode = selected.y;
						biggestXmove = 0;
						biggestYmove = 0;
					}
				}
			}

		} else {

			// Definicion
			touching = touch[InterfazControl.TOUCH_TIMER] > 0;

			if(!touching) {

				// Hay un nodo que deja de estar seleccionado
				if(selected != null) {

					// Borrando un nodo
					if(enterDeleteZone) {
						automata.remove(selected);
					}

					// Deteccion de movimiento
					if(selected != null && onGestureTime && biggestXmove > selected.radius*2 && biggestYmove > selected.radius*2 && Geometry.distance(lastXnode, lastYnode, selected.x, selected.y) < selected.radius*4) {
						eventDispatcher.dispatchEvent(new Object[]{selected}, InterfazControl.REFLEXIVE_RELATION);
						biggestXmove = 0;
						biggestYmove = 0;
					}

				}

				// Olvidando los seleccionado
				if(toJoin != null) {
					eventDispatcher.dispatchEvent(new Object[]{selected, toJoin}, InterfazControl.CONNECT_NODES);
					toJoin = null;
				}
				selected = null;

				// Lanzando eventos del left y right drag
				if(window.leftDrag) {
					if(xAbsolute > WindowLayer.GRID*3)
						eventDispatcher.dispatchEvent(null, InterfazControl.CHANGE_LEFT_WORLD);
					//					xWindow += xAbsolute;
					xAbsolute = 0;
					window.leftDrag = false;
				} else if(window.rigthDrag) {
					if(xAbsolute < width - WindowLayer.GRID*3)
						eventDispatcher.dispatchEvent(null, InterfazControl.CHANGE_RIGHT_WORLD);
					//					xWindow -= (width - xAbsolute);
					xAbsolute = 0;
					window.rigthDrag = false;
				}

			}

			// El nuevo estado permanecio siendo tocado
			else {

				// Gesture blocker
				onGestureTime = touch[InterfazControl.TOUCH_TIMER] < 950;
				msj = (onGestureTime ? "Si" : "No");
			}
		}

	}
	
	private void moveWindow(int touch[]) {
		xWindow = lastXwindow + (touch[InterfazControl.X_POS] - touch[InterfazControl.X_MOVE]);
		yWindow = lastYwindow + (touch[InterfazControl.Y_POS] - touch[InterfazControl.Y_MOVE]);
		updateEdges();
	}

	private void updateEdges() {
		for (Edge edge : automata.edges)
			((VEdge)edge).moved = true;
	}

	private void resetFlags() {
		lastXwindow = xWindow;
		lastYwindow = yWindow;
		enterDeleteZone = false;
		popUpBlocker = false;
	}

	private void findNodeByPosition(int x, int y) {
		selected = null;
		for (Node n : automata.nodes) {
			VNode nodo = (VNode)n;
			if(Geometry.distance(x, y, nodo.x, nodo.y) < nodo.radius*2.5) {
				selected = (VNode)nodo;
				break;
			}
		}
	}

	private void findNodeToJoin() {
		toJoin = null;
		for (Node n : automata.nodes) {
			VNode nodo = (VNode)n;
			if(nodo != selected && Geometry.distance(selected.x, selected.y, nodo.x, nodo.y) < nodo.radius*3) {
				toJoin = (VNode)nodo;
				break;
			}
		}
	}

	private VEdge getEdgeTouched(int x, int y) {
		for (Edge edge : automata.edges) {
			if(Geometry.distance(x, y, edge.x, edge.y) < 30)
				return (VEdge)edge;
		}
		return null;
	}

	@Override
	protected void collision() {}

	@Override
	protected void render(Canvas canvas) {

		// Lower window layer
		window.drawLowerLayer(canvas, paint, xWindow, yWindow);

		// Delete icon
		drawDeleteZone(canvas);

		// Edges
		Drawer.drawEdges(canvas, paint, automata, xWindow, yWindow, width, height);

		// Nodes
		Drawer.drawNodos(canvas, paint, automata, xWindow, yWindow, width, height);

		// Upper window layer
		window.drawUpperLayer(canvas, paint, xAbsolute);

	}

	private void drawDeleteZone(Canvas canvas) {
		if(enterDeleteZone) {
			paint.setColor(Color.LTGRAY);
			paint.setStyle(Paint.Style.FILL);
			canvas.drawCircle(DELETE_X, DELETE_Y, deleteRadius, paint);
		}
		if(selected != null)
			canvas.drawBitmap(resources.getImage(1), DELETE_X - DELETE_RADIUS + ((selected.x - xWindow) - DELETE_X)/8, DELETE_Y - DELETE_RADIUS, paint);
	}

}
